/***************************************************************************
 *   Copyright (C) 2005 by Andrzej Oczkowicz [ 2008-06-06 18:53:48 ]       *
 *   andrew_lz (at) poczta (dot) fm                                        * 
 *   amg.linuxpl.com                                                       *
 *                                                                         *
 *   Based on  usbkbd.c  by Vojtech Pavlik                                 *
 *                                                                         *
 *                                                                         *
 *   USB  Vision Plus 1022A Remote control controller                      *
 *                                                                         *
 *   for older kernels download version 0.1.3                              *
 *                                                                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

 

/* 
 #define debug 
 */


#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>

#include <asm/uaccess.h>

#define _VP_KERNEL_MODULE_
#include "vp_remote.h"



#define IOCTL_GET_HARD_VERSION	1
#define IOCTL_GET_DRV_VERSION	2

/*
 * Version Information
 */

 
#define REM_MAJOR		180
#define REM_MINOR_BASE		96
#define REM_MIX			31

#define DEVICE			"vp_remote"


#define DRIVER_VERSION "0.1.4"
#define DRIVER_AUTHOR "Andrzej Oczkowicz < andrew_lz (at) poczta (dot) fm | mod84.org >"
#define DRIVER_DESC "Vision Plus 1022A IR RC kernel module"
#define DRIVER_LICENSE "GPL"
#define VP	"VP_remote: "


MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE(DRIVER_LICENSE);


static unsigned char usb_vpremote_keycode[256] = {
	  0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
	 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
	  4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
	 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
	 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
	105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
	 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
	191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
	115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,
	122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
	150,158,159,128,136,177,178,176,142,152,173,140
};


struct usb_vpremote {
	struct input_dev *dev;
	struct usb_device *usbdev;
	unsigned char old[8];
	struct urb *irq;
	char name[128];
	char phys[64];
	int open;

	unsigned char *new;
	struct usb_ctrlrequest *cr;
	dma_addr_t cr_dma;
	dma_addr_t new_dma;
};



int show_events = 0;
module_param(show_events,      int, 0644);
MODULE_PARM_DESC(show_events, "Dont't block keyboard events from Remote Control. Default off");

int dbg = 1;
module_param(dbg,      int, 0644);
MODULE_PARM_DESC(dbg, "Print all messages. Default on");

int remap = 0;
module_param(remap,      int, 0644);
MODULE_PARM_DESC(remap, "Enable remap_table remaping. Default off");


/* device file info */	
struct dev_info {
	struct usb_device *dev;
	struct input_dev *in_dev;
	int present, opened, locked, lock_id;
	char last_code;
};


static struct dev_info vp_dev;
static char remap_table[255]; // = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, etc... }




static void set_code(int code)
{
    vp_dev.last_code = code;
#ifdef debug	
	printk(KERN_INFO "Set code: %d\n", code);
#endif
}

static void report_key(struct input_dev *dev, unsigned int code, int value)
{
	if ( remap )
		code = remap_table[code];
	if ( show_events )
		input_report_key(dev, code, value);	
	if ( value )
		set_code(code);
}

static void usb_vpremote_irq(struct urb *urb, struct pt_regs *regs)
{
	struct usb_vpremote *kbd = urb->context;
	int i;

	switch (urb->status) {
	case 0:			/* success */
		break;
	case -ECONNRESET:	/* unlink */
	case -ENOENT:
	case -ESHUTDOWN:
		return;
	/* -EPIPE:  should clear the halt */
	default:		/* error */
		goto resubmit;
	}

	for (i = 0; i < 8; i++)
		report_key(kbd->dev, usb_vpremote_keycode[i + 224], (kbd->new[0] >> i) & 1);

	for (i = 2; i < 8; i++) {

		if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {
			if (usb_vpremote_keycode[kbd->old[i]])
				report_key(kbd->dev, usb_vpremote_keycode[kbd->old[i]], 0);
			else
				info(VP"Unknown key (scancode %#x) released.", kbd->old[i]);
		}

		if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {
			if (usb_vpremote_keycode[kbd->new[i]]) {
				report_key(kbd->dev, usb_vpremote_keycode[ kbd->new[i] ], 1);
			} else
				info(VP"Unknown key (scancode %#x) pressed.", kbd->new[i]);
		}
	}

	input_sync(kbd->dev);

	memcpy(kbd->old, kbd->new, 8);

resubmit:
	i = usb_submit_urb(urb, GFP_ATOMIC);
	if (i)
		err_hid (VP"can't resubmit intr, %s-%s/input0, status %d",
				kbd->usbdev->bus->bus_name,
				kbd->usbdev->devpath, i);
}

static int usb_vpremote_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	return 0;
}

static int usb_vpremote_open(struct input_dev *dev)
{
	struct usb_vpremote *kbd = input_get_drvdata(dev);

	if (kbd->open++)
		return 0;
	vp_dev.last_code = 0;
	kbd->irq->dev = kbd->usbdev;
	if (usb_submit_urb(kbd->irq, GFP_KERNEL)) {
		kbd->open--;
		return -EIO;
	}
	return 0;
}

static void usb_vpremote_close(struct input_dev *dev)
{
	struct usb_vpremote *kbd = input_get_drvdata(dev);
	if (!--kbd->open)
		usb_kill_urb(kbd->irq);
}

static int usb_vpremote_alloc_mem(struct usb_device *dev, struct usb_vpremote *kbd)
{
	if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL)))
		return -1;
	if (!(kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma)))
		return -1;
	if (!(kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), GFP_ATOMIC, &kbd->cr_dma)))
		return -1;
	return 0;
}


/*----------------------------------------------------------------
	device file ops
------------------------------------------------------------------*/
static int fop_open(struct inode *inode, struct file *file)
{
	struct dev_info *vp =  &vp_dev;
	file->private_data = NULL;
	if (!vp->present) {
		return -EBUSY;
	}
	vp->opened++;
	if ( dbg )
		printk(KERN_INFO VP"USB VP1022A remote control opened.\n");
	return 0;
}

static int fop_close(struct inode *inode, struct file *file)
{
	struct dev_info *vp =  &vp_dev;
	if ( file->private_data ) {
		file->private_data = NULL;
		vp->locked = 0;
	}
	vp->opened--;
	if ( dbg )
		printk(KERN_INFO VP"USB VP1022A remote control closed.\n");
	return 0;
}

static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
	  unsigned long arg)
{
	struct dev_info *vp =  &vp_dev;
	int i;
	char buf[30];

	/* Sanity check  */
	if (vp == NULL ||  vp->present == 0 || vp->dev == NULL)
		return -1;
	switch (cmd) {
	case IOCTL_GET_HARD_VERSION:
		i = (vp->dev)->descriptor.bcdDevice;
		sprintf(buf,"%1d%1d.%1d%1d",(i & 0xF000)>>12,(i & 0xF00)>>8,(i & 0xF0)>>4,(i & 0xF));
		if (copy_to_user((void __user *)arg,buf,strlen(buf))!=0)
			return -EFAULT;
		break;
	case IOCTL_GET_DRV_VERSION:
		sprintf(buf,DRIVER_VERSION);
		if (copy_to_user((void __user *)arg,buf,strlen(buf))!=0)
			return -EFAULT;
		break;
	default:
		return -ENOTTY;
		break;
	}

	return 0;
}

static char *dev_get_info(struct usb_device *dev, int id)
{
	static char buffer[64];
	int i;
	for (i=0;i<64;i++)
		buffer[i] = 0;
	if ( usb_string(dev, id, buffer, 63) == 0 )
		buffer[0] = 0;
	return buffer;
}


static ssize_t fop_write(struct file *file, const char __user *buffer,
	  size_t count, loff_t * ppos)
{
	if ( vp_dev.locked && !file->private_data ) {
		if ( ! ( (count == 3 || count == 4) && (buffer[0] == 0 && buffer[1] == 0 && buffer[2] != CMD_UNLOCK_DEVICE) ) ) 
		{
			if ( dbg )
				printk(KERN_ERR VP"WRITE ERROR: Device file locked!\n");
			return 0;
		}
	}
#ifdef debug
	printk(KERN_INFO VP"write %d\n", count);
#endif
	switch ( count ){
		case 0:	{
				return 0;
			}
		case 2: {
				unsigned char idx, val;
				idx = buffer[0];
				val = buffer[1];
				if ( dbg )
					printk(KERN_INFO VP"remap_key: idx: %d, val: %d\n", idx, val);
				remap_table[idx] = val;
				return 2;
			}
		case 4:
		case 3: {
				if ( buffer[0] == 0 && buffer[1] == 0 ) {
					if ( dbg )
						printk(KERN_INFO VP"command: %d\n", buffer[2]);
					switch ( buffer[2] ) {
						case CMD_REMAP_OFF:
							{ 
								remap = 0;
								break;
							}
						case CMD_REMAP_ON:
							{
								remap = 1;
								break;
							}
						case CMD_EVENTS_OFF:
							{
								show_events = 0;
								break;
							}
						case CMD_EVENTS_ON:
							{
								show_events = 1;
								break;
							}	
						case CMD_DBG_OFF:
							{
								dbg = 0;
								break;
							}
						case CMD_DBG_ON:
							{
								dbg = 1;
								break;
							}
							
						case CMD_PRINT_INFO:
							{
 								char buf[63];
								char man[64];
								int i = vp_dev.dev->descriptor.bcdDevice;
								sprintf(buf,"%1d%1d.%1d%1d",(i & 0xF000)>>12,(i & 0xF00)>>8,(i & 0xF0)>>4,(i & 0xF));
								strcpy(man, dev_get_info(vp_dev.dev, vp_dev.dev->descriptor.iManufacturer));
								printk(KERN_INFO "REMOTE CONTROL INFO:\n"
										 "Readed command: %d, cmd len: %d\n"
										 "Device file: "DEVICE"\n"
										 "Product: %s\n"
										 "Manufacturer: %s\n"
										 "Version: %s\n"
										 "Module ver: "DRIVER_VERSION"\n"
										 "Dev file clients: %d\n"
										 , buffer[2],
										 count,
										 dev_get_info(vp_dev.dev, vp_dev.dev->descriptor.iProduct),
										 man,
										 buf,
										 vp_dev.opened
										);
										
								break;
							}
						case CMD_RAISE_EVENT:
							{
								report_key(vp_dev.in_dev, buffer[3], 1);
								report_key(vp_dev.in_dev, buffer[3], 0);
								break;
							}
						case CMD_CLEAR_TABLE:
							{
								int i;
								for (i=0;i<255;i++)
									remap_table[i] = i;
								break;
							}
						case CMD_LOCK_DEVICE:
							{
								file->private_data = (void *)20;
								vp_dev.locked = 1;
								if ( dbg )
									printk(KERN_INFO VP"Process [uid: %d, gid: %d] lock device file\n", file->f_uid, file->f_gid);
								break;
							}
						case CMD_UNLOCK_DEVICE:
							{
								file->private_data = NULL;
								vp_dev.locked = 0;
								break;
							}
					     /*
						case : etc. - another commands
					     */
					}
				} else return 2;
				return count;
			}	
	}
	return -EFAULT;
}

static ssize_t fop_read(struct file *file, char __user *buffer, size_t count, loff_t * ppos)
{
	struct dev_info *vp =  &vp_dev;
	if ( vp_dev.locked && !file->private_data ) {
#ifdef debug		
		if ( dbg )
			printk(KERN_ERR VP"READ ERROR: Device file locked!\n");
#endif			
		return 0;	
	}
		
#ifdef debug	
		printk(KERN_INFO "read /dev/"DEVICE" \n"
				"	count: %d\n"
				"	vp->present: %d\n"
				"	vp->opened: %d\n"
				"	vp->dev: %p\n",
				count,
				vp->present,
				vp->opened,
				vp->dev		
		);
#endif

	if (vp == NULL ||  vp->present == 0 || vp->dev == NULL)
		return -1;

	if ( count > 0 ){

#ifdef debug			
			if ( dbg )
				printk(KERN_INFO VP"read_key: %d\n", vp->last_code);
#endif
			char c = vp->last_code;
			vp_dev.last_code = 0;
			if (copy_to_user(buffer, &c, sizeof(c))!=0)
				return -EFAULT;
			return sizeof(c);				
		
	} else return 0;
	
}

static void usb_vpremote_free_mem(struct usb_device *dev, struct usb_vpremote *kbd)
{
	usb_free_urb(kbd->irq);
	usb_buffer_free(dev, 8, kbd->new, kbd->new_dma);
}


static struct file_operations usb_remote_fops = {
	.owner =	THIS_MODULE,
	.read  =	fop_read,
	.write =	fop_write,
	.ioctl =	fop_ioctl,
	.open  =	fop_open,
	.release =	fop_close,
};


static struct usb_class_driver usb_remote_class = {
	.name =		DEVICE,
	.fops =		&usb_remote_fops,
	.minor_base =	REM_MINOR_BASE,
};

static int usb_vpremote_probe(struct usb_interface *iface, 
			 const struct usb_device_id *id)
{
	struct usb_device * dev = interface_to_usbdev(iface);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	struct usb_vpremote *kbd;
	struct dev_info *vp =  &vp_dev;
	struct input_dev *input_dev;
	int i, pipe, maxp, retval;
	int error = -ENOMEM;
	char path[64];
	char *buf;

	if ( vp->present ) {
		printk(KERN_ERR VP"This module support only one connected Remote Control\n");
		return -1;	
	}
	
	buf = &path[0];
	if ( usb_string(dev, dev->descriptor.iProduct, buf, 63) > 0 && !strcmp(buf, "Remote Control") ){
		printk(KERN_INFO VP"Found %s ;-)\n", buf);	
	}
	 else 
		return -ENODEV;
	
	interface = iface->cur_altsetting;

	if (interface->desc.bNumEndpoints != 1)
		return -ENODEV;

	endpoint = &interface->endpoint[0].desc;
	if (!usb_endpoint_is_int_in(endpoint))
		return -ENODEV;

#ifdef CONFIG_USB_HID
	if (usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),
				le16_to_cpu(dev->descriptor.idProduct))
			& HID_QUIRK_IGNORE) {
		return -ENODEV;
	}
#endif

	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
	maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));

	kbd = kzalloc(sizeof(struct usb_vpremote), GFP_KERNEL);
	input_dev = input_allocate_device();
	if (!kbd || !input_dev){
		printk(KERN_ERR VP"input_allocate_device() return NULL, fail1!\n");
		goto fail1;
	}
	if (usb_vpremote_alloc_mem(dev, kbd)){
		printk(KERN_ERR VP"usb_vpremote_alloc_mem() return NULL, fail2!\n");
		goto fail2;
	}

	kbd->usbdev = dev;
	kbd->dev = input_dev;
	vp->dev = dev;
	vp->in_dev = input_dev;

	if (dev->manufacturer)
		strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name));

	if (dev->product) {
		if (dev->manufacturer)
			strlcat(kbd->name, " ", sizeof(kbd->name));
		strlcat(kbd->name, dev->product, sizeof(kbd->name));
	}

	if (!strlen(kbd->name))
		snprintf(kbd->name, sizeof(kbd->name),
			 "USB HIDBP Remote Control %04x:%04x",
			 le16_to_cpu(dev->descriptor.idVendor),
			 le16_to_cpu(dev->descriptor.idProduct));

	usb_make_path(dev, kbd->phys, sizeof(kbd->phys));
	strlcpy(kbd->phys, DEVICE, sizeof(kbd->phys));

	input_dev->name = kbd->name;
	input_dev->phys = kbd->phys;
	usb_to_input_id(dev, &input_dev->id);

	input_dev->dev.parent = &iface->dev;

	input_set_drvdata(input_dev, kbd);

	vp->in_dev = input_dev;
	kbd->dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
	kbd->dev->ledbit[0] = 0;

	for (i = 0; i < 255; i++)
		set_bit(usb_vpremote_keycode[i], input_dev->keybit);
	clear_bit(0, input_dev->keybit);

	input_dev->event = usb_vpremote_event;
	input_dev->open  = usb_vpremote_open;
	input_dev->close = usb_vpremote_close;

	usb_fill_int_urb(kbd->irq, dev, pipe,
			kbd->new, (maxp > 8 ? 8 : maxp),
			(usb_complete_t)usb_vpremote_irq, kbd, endpoint->bInterval);
	kbd->irq->transfer_dma = kbd->new_dma;
	kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	kbd->cr->bRequest = 0x09;
	kbd->cr->wValue = cpu_to_le16(0x200);
	kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
	kbd->cr->wLength = cpu_to_le16(1);
	
	error = input_register_device(kbd->dev);
	if (error)
		goto fail2;

	printk(KERN_INFO VP"Remote: %s on %s\n", kbd->name, path);

	usb_set_intfdata(iface, kbd);
	retval = usb_register_dev(iface, &usb_remote_class);
	if (retval) {
		err("Not able to get a minor for this device.");
		goto fail2;
	}
	printk(KERN_INFO VP"Registering RC Char Device (%s) dev_major:%d, dev_minor:%d\n", DEVICE, REM_MAJOR, REM_MINOR_BASE);
	vp->present = 1;
	return 0;

fail2:	usb_vpremote_free_mem(dev, kbd);
fail1:	input_free_device(input_dev);
	kfree(kbd);
	return -ENOMEM;
}

static void usb_vpremote_disconnect(struct usb_interface *intf)
{
	struct usb_vpremote *kbd = usb_get_intfdata (intf);
	struct dev_info *vp =  &vp_dev;
	vp->opened = 0;
	vp->present = 0;
	vp->dev = NULL;
	usb_deregister_dev(intf, &usb_remote_class);
	if ( dbg )
		printk(KERN_INFO VP"Unregistering Device (%s)\n", DEVICE);
		
	usb_set_intfdata(intf, NULL);
	if (kbd) {
		usb_kill_urb(kbd->irq);
		input_unregister_device(kbd->dev);
		usb_vpremote_free_mem(interface_to_usbdev(intf), kbd);
		kfree(kbd);
		
	}
}


static struct usb_device_id usb_vpremote_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_KEYBOARD) },
	{ }						/* Terminating entry */
};


MODULE_DEVICE_TABLE (usb, usb_vpremote_id_table);

static struct usb_driver usb_vpremote_driver = {
	.name =	"usb_vp1022a_ir_port",
	.probe =	usb_vpremote_probe,
	.disconnect =	usb_vpremote_disconnect,
	.id_table =	usb_vpremote_id_table,
};

static int usb_vpremote_init(void)
{
	int result = usb_register(&usb_vpremote_driver);
	if (result == 0)
		printk(KERN_INFO DRIVER_DESC ", version: " DRIVER_VERSION" \n");
	return result;
}

static void usb_vpremote_exit(void)
{
	usb_deregister(&usb_vpremote_driver);
}

static int __init usbvp1022air_init_module(void)
{
	int i;
	for (i=0;i<255;i++)
		remap_table[i] = i;
	vp_dev.opened = 0;
	if ( dbg )
		printk( KERN_INFO "Module usbvp1022air init\ninfo:\n"
				   "	Remap [remap] %s\n"
				   "	debug messages [dbg] %s\n",
				   remap ? "enabled" : "disabled",
				   dbg ? "enabled" : "disabled"
				);
	return usb_vpremote_init();
}

static void __exit usbvp1022air_exit_module(void)
{
	usb_vpremote_exit();
	if ( dbg )
		printk( KERN_DEBUG "Module usbvp1022air exit\n" );
}

module_init(usbvp1022air_init_module);
module_exit(usbvp1022air_exit_module);
